﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Reflection;

namespace WebPALTT.Lib
{
    /// <summary>
    /// Defines a test instance to be run.
    /// </summary>
    [Serializable]
    public class TestDefinition : ISerializable
    {
        List<Scenario> mScenarios = new List<Scenario> { };
        RunMode mRunMode = RunMode.Concurrent;
        List<KeyValuePair<String, String>> mMessageLog = new List<KeyValuePair<string, string>> { };
        List<ScenarioResults> mResults = new List<ScenarioResults> { };
        TimeSpan mRunDuration;

        #region Properties        
        /// <summary>
        /// Gets or set the array of user sessions defined in this test instance.
        /// </summary>
        public List<Scenario> Scenarios { get { return mScenarios; } }

        /// <summary>
        /// Gets or set the run mode of the test instance. Can be concurrent or sequential.
        /// </summary>
        public RunMode RunMode
        {
            get { return mRunMode; }
            set { mRunMode = value; }
        }

        /// <summary>
        /// Gets the message log as an array of key value pairs. 
        /// Key = Message
        /// Value = Error Information and Stacktrace
        /// 
        /// NB: Message log is not cleared when the tests are rerun.
        /// </summary>
        public List<KeyValuePair<String, String>> MessageLog
        {
            get { return mMessageLog; }
        }
        /// <summary>
        /// Clears the message log.
        /// </summary>
        public void ClearMessageLog() { mMessageLog.Clear(); }

        /// <summary>
        /// Gets the results for the last test run.
        /// </summary>
        public List<ScenarioResults> Results { get { return mResults; } }
        public TimeSpan RunDuration { get { return mRunDuration; } set { mRunDuration = value; } }
        #endregion

        /// <summary>
        /// Creates a default test instance.
        /// </summary>
        public TestDefinition()
        { }

        public TestDefinition(SerializationInfo info, StreamingContext ctxt)
        {
            mScenarios = (List<Scenario>)info.GetValue("Scenarios", typeof(List<Scenario>));
            mRunMode = (RunMode)info.GetValue("RunMode", typeof(RunMode));
            mRunDuration = (TimeSpan)info.GetValue("RunDuration", typeof(TimeSpan));
            try { mMessageLog = (List<KeyValuePair<String, String>>)info.GetValue("MessageLog", typeof(List<KeyValuePair<String, String>>)); }
            catch (Exception) { mMessageLog = new List<KeyValuePair<string, string>> { }; }
            try { mResults = (List<ScenarioResults>)info.GetValue("Results", typeof(List<ScenarioResults>)); }
            catch (Exception) { mResults = new List<ScenarioResults> { }; }
        }

        public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
        {
            info.AddValue("Scenarios", mScenarios);
            info.AddValue("RunMode", mRunMode);
            info.AddValue("RunDuration", mRunDuration);
            info.AddValue("MessageLog", mMessageLog);
            info.AddValue("Results", mResults);
        }

        public void RunTest()
        {
            this.mResults = null;
            this.ClearMessageLog();            
            if (this.mScenarios.Count == 0)
            {
                mMessageLog.Add(new KeyValuePair<string, string>("No scenarios are assigned to the project", "The project must contain atleast one scenario."));
                return;
            }

            ProgressWindow progressWin = new ProgressWindow("Test Execution in Progress...");
            progressWin.SetValue(5, "Compiling Script Code...");
            
            Assembly compiledAssembly = this.CompileAssembly();
            if (compiledAssembly == null)
                return;

            this.mResults = new List<ScenarioResults> { };
            Stopwatch timer = new Stopwatch();
            timer.Start();

            ManualResetEvent threadsFinished = new ManualResetEvent(false);
            
            progressWin.SetValue(25, "Running Test...");
            ThreadWorker worker = null;
            switch (this.RunMode)
            {
                case RunMode.Concurrent:
                    int maxCount = this.Scenarios.Count;
                    worker = new ThreadWorker(maxCount, compiledAssembly);
                    worker.ThreadsFinished = threadsFinished;

                    for (int i = 0; i < this.Scenarios.Count; i++)
                        ThreadPool.QueueUserWorkItem(new WaitCallback(worker.SceneWorker), new object[] { this.Scenarios[i], "Scenario_" + (i + 1) });

                    threadsFinished.WaitOne(Timeout.Infinite, true);
                    break;
                case RunMode.Sequential:
                    worker = new ThreadWorker(1, compiledAssembly);
                    worker.ThreadsFinished = threadsFinished;

                    for (int i = 0; i < mScenarios.Count; i++)
                    {
                        worker.Reset();
                        ThreadPool.QueueUserWorkItem(new WaitCallback(worker.SceneWorker), new object[] { mScenarios[i], "Scenario_" + (i + 1) });
                        threadsFinished.WaitOne(Timeout.Infinite, true);
                    }
                    break;
            }

            timer.Stop();
            this.mRunDuration = timer.Elapsed;

            if (worker != null)
            {
                mMessageLog.AddRange(worker.Log);
                mResults.AddRange(worker.Results);
            }
            progressWin.Close();
        }

        public Assembly CompileAssembly()
        {
            List<string> assemblyNames = new List<string> { "System.dll", "mscorlib.dll", "System.Core.dll", "WebPALTT.Lib.dll" };
            List<string> code = new List<string> { };
            for (int i=0; i<mScenarios.Count;i++)
            {
                foreach (string str in mScenarios[i].CompileParameters)
                    if (!assemblyNames.Contains(str))
                        assemblyNames.Add(str);
                code.Add(mScenarios[i].GetScriptCode("Scenario_" + (i + 1)));
            }

            CodeDomProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
            CompilerParameters mCompileParams = new CompilerParameters(assemblyNames.ToArray());
            mCompileParams.GenerateInMemory = true;
            mCompileParams.CompilerOptions = "/target:library /optimize";

            CompilerResults results = provider.CompileAssemblyFromSource(mCompileParams, code.ToArray());
            if (results.Errors.HasErrors)
            {
                foreach (CompilerError err in results.Errors)
                    mMessageLog.Add(new KeyValuePair<string,string>("Compile Error", "File: " + err.FileName + "; Line (" + err.Line + ") - " + err.ErrorText + "\n"));
                return null;
            }
            return results.CompiledAssembly;
        }
    }

    public enum RunMode
    { Concurrent, Sequential, RampUp }
}
